package com.dx.security.provider;

import cn.hutool.core.date.DateUtil;
import com.dx.security.dto.AccessToken;
import com.dx.security.properties.JwtProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * @author : Yu.Tan
 * @date : 2023/7/22 19:44
 * @desc : this class for xxxxx
 **/
@Slf4j
@Component
public class JwtProvider {
    @Autowired
    private JwtProperties jwtProperties;


    /**
     * 从请求中获取token
     *
     * @param request 请求
     * @return
     */
    public String getToken(HttpServletRequest request) {
        return request.getHeader(jwtProperties.getRequestHeader());
    }

    /**
     * 根据用户信息生成Token
     *
     * @param userDetails 用户信息
     * @return
     */
    public AccessToken createToken(UserDetails userDetails) {
        return createToken(userDetails.getUsername());
    }

    /**
     * 生成Token
     *
     * @param subject 加入Token的字符串
     * @return
     */
    public AccessToken createToken(String subject) {
        //当前时间
        final Date now = new Date();
        //过期时间
        final Date expirationDate = new Date(now.getTime() + jwtProperties.getExpirationTime() * 1000);

        String token = jwtProperties.getTokenPrefix() + Jwts.builder()
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, jwtProperties.getApiSecretKey())
                .compact();
        return AccessToken.builder().loginAccount(subject).token(token).expirationTime(expirationDate).build();
    }

    /**
     * 验证token是否还有效
     * <p>
     * 反解析出token中信息，然后与参数中的信息比较，再校验过期时间
     *
     * @param token       客户端传入的token
     * @param userDetails 从数据库中查询出来的用户信息
     * @return
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        Claims claims = getClaimsFromToken(token);
        return claims.getSubject().equals(userDetails.getUsername()) && !isTokenExpiration(claims);
    }

    /**
     * 刷新token
     * 过滤器会对请求进行验证，所以这里可以不必验证
     *
     * @param token 带tokenHead的token
     * @return
     */
    public AccessToken refreshToken(String token) {
        String subToken = token.substring(jwtProperties.getTokenPrefix().length());
        //解析token
        Claims claims = getClaimsFromToken(subToken);

        //如果token在30分钟之内刷新过，返回原token
        if (tokenRefreshJustBefore(claims)) {
            return AccessToken.builder().loginAccount(claims.getSubject()).token(token).expirationTime(claims.getExpiration()).build();
        } else {
            return createToken(claims.getSubject());
        }

    }

    /**
     * 判断token在指定时间内是否刚刚刷新过
     *
     * @param claims token的负载信息
     * @return
     */
    private boolean tokenRefreshJustBefore(Claims claims) {
        Date refreshDate = new Date();
        //刷新时间在创建时间的指定时间范围内
        if (refreshDate.after(claims.getExpiration()) && refreshDate.before(DateUtil.offsetSecond(claims.getExpiration(), 1800))) {
            return true;
        }
        return false;
    }

    /**
     * 从token中拿到负载信息
     *
     * @param token token信息
     * @return
     */
    public Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser().setSigningKey(jwtProperties.getApiSecretKey()).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return claims;
    }

    /**
     * 判断token是否过期
     *
     * @param claims 负载信息
     * @return
     */
    private boolean isTokenExpiration(Claims claims) {
        return claims.getExpiration().before(new Date());
    }

    /**
     * 从Token中获取主题
     * @param token
     * @return
     */
    public String getSubjectFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        if (claims != null) {
            return claims.getSubject();
        }
        return null;
    }

}

